package com.wchat.network.device;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map.Entry;

import android.content.Context;
import android.content.Intent;

import com.wchat.network.Actions;
import com.wchat.network.Configs;
import com.wchat.network.message.Message;
import com.wchat.network.message.WhoAmIMessage;

public class DeviceContext {

	private Context context;
	
	private Device local;

	private HashMap<Long, Device> devices = new LinkedHashMap<Long, Device>();

	private HashMap<Long, Group> groups = new LinkedHashMap<Long, Group>();

	private LinkedList<DeviceGroupLink> links = new LinkedList<DeviceGroupLink>();
	
	private CheckExpireThread checkExpireThread;
	
	public void startCheckExpire(Context context) {
		this.context = context;
		checkExpireThread = new CheckExpireThread();
		checkExpireThread.start();
	}
	
	public void stopCheckExpire() {
		checkExpireThread.close();
		try {
			checkExpireThread.join();
		} catch (Exception e) {
			
		}
		checkExpireThread = null;
		this.context = null;
	}
	
	public synchronized void clean() {
		devices.clear();
		groups.clear();
		links.clear();
	}

	public Device getLocal() {
		return local;
	}

	public void setLocal(Device local) {
		this.local = local;
	}

	public synchronized ArrayList<Device> getDeviceList() {
		ArrayList<Device> result = new ArrayList<Device>();
		for (Iterator<Entry<Long, Device>> iterator = devices.entrySet().iterator(); iterator.hasNext();) {
			result.add(iterator.next().getValue());
		}
		return result;
	}

	public synchronized ArrayList<Group> getGroups() {
		ArrayList<Group> result = new ArrayList<Group>();
		for (Iterator<Entry<Long, Group>> iterator = groups.entrySet().iterator(); iterator.hasNext();) {
			result.add(iterator.next().getValue());
		}
		return result;
	}

	public synchronized Device getDeviceById(long deviceId) {
		if (deviceId == local.getId())
			return local;
		return devices.get(deviceId);
	}

	public synchronized Group getGroupById(long groupId) {
		return groups.get(groupId);
	}

	public synchronized ArrayList<Device> getDevicesInGroup(long groupId) {
		ArrayList<Device> devices = new ArrayList<Device>();
		for (Iterator<DeviceGroupLink> iterator = links.iterator(); iterator.hasNext();) {
			DeviceGroupLink link = iterator.next();
			if (link.groupId == groupId) {
				Device device = getDeviceById(link.deviceId);
				if (device != null) {
					devices.add(device);
				} else {
					iterator.remove();
					Configs.i("!!!!!!!!!!!!!!!!!!!!!!!!!!!!! device lost, but have group info");
				}
			}
		}
		return devices;
	}
	
	private synchronized int getDeviceCountInGroup(long groupId) {
		int count = 0;
		for (Iterator<DeviceGroupLink> iterator = links.iterator(); iterator.hasNext();) {
			DeviceGroupLink link = iterator.next();
			if (link.groupId == groupId) {
				count++;
			}
		}
		return count;
	}

	public synchronized void analyseBroadcast(Context context, WhoAmIMessage whoAmIMessage) {
		InetAddress from = whoAmIMessage.getFrom();
		Device contextDevice = devices.get(whoAmIMessage.getDeviceId());
		Device messageDevice = whoAmIMessage.getDevice();
		messageDevice.setAddress(from);
		/* check device */
		if (contextDevice == null) {
			messageDevice.refresh();
			devices.put(messageDevice.getId(), messageDevice);
			Intent intent = new Intent(Actions.ACTION_DEVICE_FOUND);
			intent.putExtra("device", messageDevice);
			context.sendBroadcast(intent);
		} else {
			if (!contextDevice.equals(messageDevice)) {
				messageDevice.refresh();
				devices.put(messageDevice.getId(), messageDevice);
				Intent intent = new Intent(Actions.ACTION_DEVICE_UPDATED);
				intent.putExtra("device", messageDevice);
				context.sendBroadcast(intent);
			} else {
				contextDevice.refresh();
			}
		}
		/* check group */
		ArrayList<Group> groups = whoAmIMessage.getGroups();
		int size = groups.size();
		for (int i = 0; i < size; i++) {
			Group group = groups.get(i);
			addGroup(context, group, messageDevice);
		}
	}
	
	private synchronized void doDeviceLeave(Context context, Device device) {
		if (device == null)
			return;
		Intent intent = new Intent(Actions.ACTION_DEVICE_LOST);
		intent.putExtra("device", device);
		context.sendBroadcast(intent);
		long deviceId = device.getId();
		/* check if device joins some group */
		for (Iterator<DeviceGroupLink> iterator = links.iterator(); iterator.hasNext();) {
			DeviceGroupLink link = iterator.next();
			if (link.deviceId == deviceId) {
				iterator.remove();
				Group group = getGroupById(link.groupId);
				if (group != null) {
					int count = getDeviceCountInGroup(link.groupId);
					if (count != 0) {
						intent = new Intent(Actions.ACTION_GROUP_UPDATED);
						intent.putExtra("group", getGroupById(link.groupId));
						context.sendBroadcast(intent);
					} else {
						intent = new Intent(Actions.ACTION_GROUP_REMOVED);
						Group g = groups.remove(link.groupId);
						intent.putExtra("group", g);
						context.sendBroadcast(intent);
					}
				}
				return;
			}
		}
	}
	
	public synchronized void deviceLeave(Context context, long deviceId) {
		Device device = devices.remove(deviceId);
		doDeviceLeave(context, device);
	}

	private synchronized void checkExpire() {
		/* check device */
		for (Iterator<Entry<Long, Device>> iterator = devices.entrySet().iterator(); iterator.hasNext();) {
			Device device = iterator.next().getValue();
			if (Configs.AUTO_REMOVE_DEVICE_ENABLE && device.isExpired()) {
				iterator.remove();
				doDeviceLeave(context, device);
			}
		}
	}

	public synchronized void addGroup(Context context, Group group, Device device) {
		long gid = group.getGroupid();
		Group contextGroup = groups.get(gid);
		groups.put(gid, group);
		if (contextGroup == null) {
			links.add(new DeviceGroupLink(device.getId(), gid));
			Intent intent = new Intent(Actions.ACTION_GROUP_FOUND);
			intent.putExtra("group", group);
			context.sendBroadcast(intent);
		} else {
			/* update or add a link */
			updateOrAddLink(context, device.getId(), gid);
		}
	}

	private void updateOrAddLink(Context context, long deviceId, long groupId) {
		for (Iterator<DeviceGroupLink> iterator = links.iterator(); iterator.hasNext();) {
			DeviceGroupLink link = iterator.next();
			if (link.deviceId == deviceId && link.groupId == groupId) {
				return;
			}
		}
		links.add(new DeviceGroupLink(deviceId, groupId));
		Intent intent = new Intent(Actions.ACTION_GROUP_UPDATED);
		intent.putExtra("group", getGroupById(groupId));
		context.sendBroadcast(intent);
	}

	public synchronized void leaveGroup(Context context, long groupId, long deviceId) {
		for (Iterator<DeviceGroupLink> iterator = links.iterator(); iterator.hasNext();) {
			DeviceGroupLink link = iterator.next();
			if (link.deviceId == deviceId && link.groupId == groupId) {
				iterator.remove();
				int count = getDeviceCountInGroup(groupId);
				if (count != 0) {
					Intent intent = new Intent(Actions.ACTION_GROUP_UPDATED);
					intent.putExtra("group", getGroupById(groupId));
					context.sendBroadcast(intent);
				} else {
					Intent intent = new Intent(Actions.ACTION_GROUP_REMOVED);
					Group g = groups.remove(groupId);
					intent.putExtra("group", g);
					context.sendBroadcast(intent);
				}
				return;
			}
		}
	}

	public synchronized Device getMessageDevice(Message message) {

		if (message.getDeviceId() == local.getId())
			return local;
		
		return devices.get(message.getDeviceId());
	}

	class CheckExpireThread extends Thread {

		volatile boolean close = false;
		Object lock = new Object();

		@Override
		public void run() {
			while (!close) {
				checkExpire();
				synchronized (lock) {
					try {
						lock.wait(Configs.CHECK_EXPIRE_INTERVAL);
					} catch (InterruptedException e) {
						return;
					}
				}
			}

		}
		
		void close() {
			close = true;
			synchronized (lock) {
				lock.notify();
			}
		}
	}

}
